package com.lagou.dubbo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.support.RpcUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;


@Activate(group = {CommonConstants.CONSUMER})
public class TPMonitorFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(TPMonitorFilter.class);

    private static volatile Map<String, List<Monitor>> monitorCache = new ConcurrentHashMap<>();

    private static volatile Boolean isMonitoring = false;

    private static ReadWriteLock lock = new ReentrantReadWriteLock();

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 添加打印任务
        if (!isMonitoring) {
            synchronized (TPMonitorFilter.class) {
                isMonitoring = true;
                new Thread(() -> printMonitorInfo()).start();
            }
        }

        long start = System.currentTimeMillis();
        Result invoke = invoker.invoke(invocation);
        boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation);
        if (!async) {
            long end = System.currentTimeMillis();
            long executedPeriod = end - start;
            String methodName = invocation.getMethodName();
            addMonitoring(methodName, start, executedPeriod);
            logger.info("------- method[{}]执行时间为:{}", methodName, executedPeriod);
        }
        return invoke;
    }

    /**
     * 添加方法的执行时间和执行耗时
     *
     * @param methodName    执行的方法名称
     * @param executeTime   方法开始执行的时间
     * @param executePeriod 执行方法耗费的时间
     */
    private static void addMonitoring(String methodName, Long executeTime, Long executePeriod) {
        List<Monitor> monitors = monitorCache.get(methodName);
        while (monitors == null) {
            lock.writeLock().lock();
            try {
                monitors = new CopyOnWriteArrayList<>();
                monitorCache.put(methodName, monitors);
            } finally {
                lock.writeLock().unlock();
            }
        }
        monitors.add(new Monitor(executeTime, executePeriod));
    }

    private static final Long oneMinute = 1 * 60 * 1000l;

    private void printMonitorInfo() {
        while (true) {
            Long now = System.currentTimeMillis();
            Long startTime = now - oneMinute;
            monitorCache.keySet().forEach(methodName -> printPercent(methodName, 0.9f, startTime));

            sleep(3000);

            monitorCache.keySet().forEach(methodName -> printPercent(methodName, 0.99f, startTime));

            sleep(2000);
        }
    }

    private void sleep(int sleepTime) {
        try {
            TimeUnit.MILLISECONDS.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 打印 percent 请求通过率的耗时
     *
     * @param percent   请求通过率
     * @param startTime 开始统计时间
     */
    public void printPercent(String methodName, Float percent, Long startTime) {
        lock.readLock().lock();
        List<Monitor> monitors = new ArrayList<>();
        try {
            monitors = monitorCache.get(methodName);
        } finally {
            lock.readLock().unlock();
        }
        // 获取指定时间段内的调用记录
        List<Long> monitorList = monitors.stream()
                .filter(monitor -> monitor.after(startTime))
                .map(monitor -> monitor.getExecutePeriod())
                .sorted()
                .collect(Collectors.toList());
        int invokeCount = monitorList.size();
        int targetCount = (int)Math.ceil(invokeCount * percent);
        int targetIndex = targetCount -1;
        logger.info("---method [{}] 的 {}% 执行时间为: {} 毫秒 ----",
                methodName,
                percent * 100,
                targetIndex > 0 ? monitorList.get(targetIndex) : "未执行");
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    private static class Monitor {
        private Long executeTime;
        private Long ExecutePeriod;

        public Boolean after(Long timestamp) {
            return this.executeTime - timestamp >= 0;
        }
    }
}
